<?php


namespace common\components\yii;

use Yii;
use yii\web\UrlRule;
use yii\web\UrlRuleInterface;
use yii\web\UrlManager as YiiUrlManager;
use yii\helpers\ArrayHelper;


class UrlManager extends YiiUrlManager
{
    private $_ruleCache;

    /**
     * {@inheritdoc}
     */
    public function createUrl($params)
    {
        $params = (array)$params;
        $anchor = isset($params['#']) ? '#' . $params['#'] : '';
        unset($params['#'], $params[$this->routeParam]);

        $route = trim($params[0], '/');
        unset($params[0]);

        $baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl();

        if ($this->enablePrettyUrl) {
            $cacheKey = $route . '?';
            foreach ($params as $key => $value) {
                if ($value !== null) {
                    $cacheKey .= $key . '&';
                }
            }

            $url = $this->getUrlFromCache($cacheKey, $route, $params);
            if ($url === false) {
                /* @var $rule UrlRule */
                foreach ($this->rules as $rule) {
                    if (in_array($rule, $this->_ruleCache[$cacheKey], true)) {
                        // avoid redundant calls of `UrlRule::createUrl()` for rules checked in `getUrlFromCache()`
                        // @see https://github.com/yiisoft/yii2/issues/14094
                        continue;
                    }
                    $url = $rule->createUrl($this, $route, $params);
                    if ($this->canBeCached($rule)) {
                        $this->setRuleToCache($cacheKey, $rule);
                    }
                    if ($url !== false) {
                        break;
                    }
                }
                if ($url === false) {
                    $url = $this->creatQueryUrl($route, $params);
                }
            }

            if ($url !== false) {
                if (strpos($url, '://') !== false) {
                    if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
                        return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor;
                    }

                    return $url . $baseUrl . $anchor;
                } elseif (strncmp($url, '//', 2) === 0) {
                    if ($baseUrl !== '' && ($pos = strpos($url, '/', 2)) !== false) {
                        return substr($url, 0, $pos) . $baseUrl . substr($url, $pos) . $anchor;
                    }

                    return $url . $baseUrl . $anchor;
                }

                $url = ltrim($url, '/');
                return "$baseUrl/{$url}{$anchor}";
            }

            if ($this->suffix !== null) {
                $route .= $this->suffix;
            }
            if (!empty($params) && ($query = http_build_query($params)) !== '') {
                $route .= '?' . $query;
            }

            $route = ltrim($route, '/');
            return "$baseUrl/{$route}{$anchor}";
        }

        $url = "$baseUrl?{$this->routeParam}=" . urlencode($route);
        if (!empty($params) && ($query = http_build_query($params)) !== '') {
            $url .= '&' . $query;
        }

        return $url . $anchor;
    }

    /**
     * {@inheritdoc}
     */
    public function parseRequest($request)
    {
        if ($this->enablePrettyUrl) {
            /* @var $rule UrlRule */
            foreach ($this->rules as $rule) {
                $result = $rule->parseRequest($this, $request);
                if (YII_DEBUG) {
                    Yii::debug([
                        'rule' => method_exists($rule, '__toString') ? $rule->__toString() : get_class($rule),
                        'match' => $result !== false,
                        'parent' => null,
                    ], __METHOD__);
                }
                if ($result !== false) {
                    return $result;
                }
            }

            $result = $this->parseQueryRequest($request);
            if ($result !== false) {
                return $result;
            }

            if ($this->enableStrictParsing) {
                return false;
            }

            Yii::debug('No matching URL rules. Using default URL parsing logic.', __METHOD__);

            $suffix = (string)$this->suffix;
            $pathInfo = $request->getPathInfo();
            $normalized = false;
            if ($this->normalizer !== false) {
                $pathInfo = $this->normalizer->normalizePathInfo($pathInfo, $suffix, $normalized);
            }
            if ($suffix !== '' && $pathInfo !== '') {
                $n = strlen($this->suffix);
                if (substr_compare($pathInfo, $this->suffix, -$n, $n) === 0) {
                    $pathInfo = substr($pathInfo, 0, -$n);
                    if ($pathInfo === '') {
                        // suffix alone is not allowed
                        return false;
                    }
                } else {
                    // suffix doesn't match
                    return false;
                }
            }

            if ($normalized) {
                // pathInfo was changed by normalizer - we need also normalize route
                return $this->normalizer->normalizeRoute([$pathInfo, []]);
            }

            return [$pathInfo, []];
        }

        Yii::debug('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
        $route = $request->getQueryParam($this->routeParam, '');
        if (is_array($route)) {
            $route = '';
        }

        return [(string)$route, []];
    }

    /**
     * {@inheritdoc}
     */
    protected function getUrlFromCache($cacheKey, $route, $params)
    {
        if (!empty($this->_ruleCache[$cacheKey])) {
            foreach ($this->_ruleCache[$cacheKey] as $rule) {
                /* @var $rule UrlRule */
                if (($url = $rule->createUrl($this, $route, $params)) !== false) {
                    return $url;
                }
            }
        } else {
            $this->_ruleCache[$cacheKey] = [];
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    protected function setRuleToCache($cacheKey, UrlRuleInterface $rule)
    {
        $this->_ruleCache[$cacheKey][] = $rule;
    }

    /**
     * 根据数据库库查询生成 URL
     * @param string $route 路由
     * @param array $params 参数
     * @return bool|string
     */
    protected function creatQueryUrl($route, $params)
    {
        $query = new \yii\db\Query();
        $client = $query->from('{{%clients}}')
            ->where(['status' => 1, 'name' => basename(Yii::$app->basePath)])
            ->one();
        $menuTypes = $query->from('{{%menu_types}}')->where(['client_id' => $client['id']])->all();
        $menus = $query->from('{{%menu}}')
            ->select(['id', 'path', 'type', 'route', 'request'])
            ->where([
                'published' => 1,
                'menutype' => ArrayHelper::getColumn($menuTypes, 'menutype'),
                'language' => ['*', Yii::$app->language]
            ])
            ->all();

        $menu = null;
        foreach ($menus as $item) {
            $request = json_decode($item['request'], true) ?? [];
            if ($item['type'] === 'module') {
                if ($item['route'] === '@' . Yii::$app->id . '/' . $route && ArrayHelper::isSubset($request, $params)) {
                    $menu = $item;
                    break;
                }
            } elseif ($item['type'] === 'url') {
                if ($item['route'] === $route && ArrayHelper::isSubset($request, $params)) {
                    $menu = $item;
                    break;
                }
            } elseif ($item['type'] === 'alias') {
                if ($menu['id'] === $params['mid']) {
                    $menu = $item;
                    break;
                }
            }
        }

        if ($menu) {
            $request = json_decode($menu['request'], true) ?? [];
            $rule = new UrlRule([
                'pattern' => $menu['path'],
                'route' => $route,
                'defaults' => array_merge($request, ['mid' => $menu['id']])
            ]);
            $url = $rule->createUrl($this, $route, array_merge($params, ['mid' => $menu['id']]));
        } else {
            if (count(explode('/', $route)) === 3) {
                $module = Yii::$app->getModule(strstr($route, '/', true));
                $ruleConfig = $module->creatUrl(strstr($route, '/'), $params);
                $rule = new UrlRule($ruleConfig);
                $url = $rule->createUrl($this, $route, $params);
            } else {
                $url = false;
            }
        }
        return $url;
    }

    public function parseQueryRequest($request)
    {
        $normalized = false;
        $pathInfo = $request->pathInfo;
        $suffix = (string)$this->suffix;
        if ($this->normalizer !== false) {
            $pathInfo = $this->normalizer->normalizePathInfo($pathInfo, $suffix, $normalized);
        }

        if ($suffix !== '' && $pathInfo !== '') {
            $n = strlen($suffix);
            if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) {
                $pathInfo = substr($pathInfo, 0, -$n);
                if ($pathInfo === '') {
                    // suffix alone is not allowed
                    return false;
                }
            } else {
                return false;
            }
        }

        $result = false;
        $query = new \yii\db\Query();
        if ($pathInfo === '') {
            $client = $query->from('{{%clients}}')
                ->where(['status' => 1, 'name' => basename(Yii::$app->basePath)])
                ->one();
            $menuTypes = $query->from('{{%menu_types}}')->where(['client_id' => $client['id']])->all();
            $menu = $query->from('{{%menu}}')
                ->select(['id', 'alias', 'route', 'request'])
                ->where([
                    'published' => 1,
                    'menutype' => ArrayHelper::getColumn($menuTypes, 'menutype'),
                    'home' => 1,
                    'language' => ['*', Yii::$app->language]
                ])
                ->one();
            if ($menu) {
                $rule = new UrlRule([
                    'pattern' => '',
                    'route' => strstr($menu['route'], '/'),
                    'defaults' => json_decode($menu['request'], true) ?? []
                ]);
                $result = $rule->parseRequest($this, $request);
            }
        } elseif (strpos($pathInfo, '_') === 0) {
            $category = $query->from('{{%categories}}')
                ->select(['id', 'path', 'extension', 'language'])
                ->where([
                    'path' => substr($pathInfo, 1),
                    'published' => 1,
                    'language' => ['*', Yii::$app->language]
                ])
                ->one();
            if ($category) {
                $rule = new UrlRule([
                    'pattern' => $pathInfo,
                    'route' => '/content/default/index',
                    'defaults' => [
                        'cid' => $category['id']
                    ]
                ]);
            } else {
                $pathInfoArray = explode('/', $pathInfo);
                $id = array_pop($pathInfoArray);
                if (is_numeric($id)) {
                    $category = $query->from('{{%categories}}')
                        ->select(['id', 'path', 'extension', 'language'])
                        ->where([
                            'id' => $id,
                            'published' => 1,
                            'language' => ['*', Yii::$app->language]
                        ])
                        ->one();
                    if ($category) {
                        $rule = new UrlRule([
                            'pattern' => implode('/', $pathInfoArray) . '/<id:\d+>',
                            'route' => '/content/default/view'
                        ]);
                    }
                }
                if (!boolval($rule)) {
                    $rule = new UrlRule([
                        'pattern' => 'error',
                        'route' => '/site/error'
                    ]);
                }
            }
            $result = $rule->parseRequest($this, $request);
        } else {
            $client = $query->from('{{%clients}}')
                ->where(['status' => 1, 'name' => basename(Yii::$app->basePath)])
                ->one();
            $menuTypes = $query->from('{{%menu_types}}')->where(['client_id' => $client['id']])->all();
            $menu = $query->from('{{%menu}}')
                ->select(['id', 'alias', 'path', 'type', 'route', 'request'])
                ->where([
                    'path' => $pathInfo,
                    'published' => 1,
                    'menutype' => ArrayHelper::getColumn($menuTypes, 'menutype'),
                    'language' => ['*', Yii::$app->language]
                ])
                ->one();
            if ($menu) {
                if ($menu['type'] === 'module') {
                    $rule = new UrlRule([
                        'pattern' => $pathInfo,
                        'route' => strstr($menu['route'], '/'),
                        'defaults' => array_merge(json_decode($menu['request'], true) ?? [], ['mid' => $menu['id']])
                    ]);
                    $result = $rule->parseRequest($this, $request);
                } elseif ($menu['type'] === 'url') {
                    $rule = new UrlRule([
                        'pattern' => $pathInfo,
                        'route' => '/site/redirect',
                        'defaults' => array_merge(['url' => $menu['route']], json_decode($menu['request'], true) ?? [])
                    ]);
                    $result = $rule->parseRequest($this, $request);
                }
            }
        }
        return $result;
    }
}